# Copyright (c) 2006 Charl P. Botha <http://cpbotha.net/>
# All rights reserved.

# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
# 3. The name of the author may not be used to endorse or promote products
#    derived from this software without specific prior written permission.

# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

from ee_except import EnvEditException, EnvEditWarningException
import os
import re
from Tkinter import *
from ScrolledText import ScrolledText
import tkMessageBox
import tkFileDialog
import tkSimpleDialog
from utils import ci_str_cmp
from widget_redirector import WidgetRedirector

MODE_SYNCED=0
MODE_MODIFIED=1
MODE_PREVIEW=2

MODE_TO_TEXT = {MODE_SYNCED : "= SYNCHRONISED =",
                MODE_MODIFIED : "* MODIFIED *",
                MODE_PREVIEW : "- PREVIEW -"}

CURTEXT_USER=0
CURTEXT_SYS=1

#########################################################################
class PathEditDialog(tkSimpleDialog.Dialog):
    """Dialog with which the current environment value can be edited as
    a list of paths.

    After you've instantiated this dialog, you can query the new_value
    instance variable.  If this is None, the user clicked on Cancel.
    If there's a string (even a '' zero-length one), the user clicked on
    Apply.
    """

    def __init__(self, parent, paths, title=None, iconpath=None):
        """ctor.

        @param paths: a list of paths that will be edited.
        @param title: title of the dialog.
        @param iconpath: path to the application icon.
        """
        
        self.new_value = None
        self.iconpath = iconpath
        self.paths = paths

        tkSimpleDialog.Dialog.__init__(self, parent, title)

    def body(self, master):
        """This is called by the parent in order to build up the dialog.
        """
        
        # set our icon
        if self.iconpath:
            self.iconbitmap(self.iconpath)

        #
        menu = Menu(self)
        self.config(menu=menu)

        menu.add_command(label="Alpha Sort", underline=0,
                         command=self.cmd_alpha_sort)
        menu.add_command(label="Insert directory", underline=7,
                         command=self.cmd_insert_dir)
        menu.add_command(label="Insert file path", underline=7,
                         command=self.cmd_insert_path)
        
        #menu.add_command(label="Replace with Dir", underline=0,
        #                 command=self.cmd_replace_dir)

        #separator = Frame(master, height=2, bd=1, relief=SUNKEN)
        #separator.pack(fill=X, padx=5, pady=2)
        

        # make our parent fill the dialog
        master.pack(fill=BOTH, expand=1)

        # create text control and make it fill our parent
        self.text = ScrolledText(master=master, height=15, width=40, wrap=N)
        self.text.pack(fill=BOTH, expand=1)

        # rest of UI init ################################################

        # and finally insert our initial text
        self.text.insert(END, '\n'.join(self.paths))

        # our text control should have the focus
        return self.text

    def buttonbox(self):
        """Called by parent class to build the conventional OK and Cancel
        buttons.

        We have overrided this with more meaningful button texts and different
        key bindings.  <Return> should add a new line to the text dialog.
        We'll use <Control-Return> to activate the Apply button.
        """

        # this would be one way to change the default binding of the
        # <Return> key (OKing the dialog):
        #tkSimpleDialog.Dialog.buttonbox(self)
        #self.unbind('<Return>')
        #self.bind('<Control-Return>', self.ok)

        # since we want other buttons, we have to redo the whole thing
        # in anycase:

        box = Frame(self)

        w = Button(box, text="Apply", width=15, command=self.ok,
                   default=ACTIVE)
        w.pack(side=LEFT, padx=5, pady=5)
        w = Button(box, text="Cancel", width=15, command=self.cancel)
        w.pack(side=LEFT, padx=5, pady=5)

        self.bind("<Control-Return>", self.ok)
        self.bind("<Escape>", self.cancel)

        box.pack()

        self.update_idletasks()
        self.minsize(self.winfo_width(), self.winfo_height())

    def apply(self):
        """Method that's called if the user clicks on OK.
        """

        # get all text (already stripped and checked for empty)
        lines = self.get_lines()
        # add it all together
        self.new_value = ';'.join(lines)

    def cmd_alpha_sort(self, event=None):
        """Sort the lines in the text alphabetically.
        """

        lines = self.get_lines()
        # sort it
        lines.sort(ci_str_cmp)
        # join it back into text
        t = '\n'.join(lines)
        # and then stuff it back into the text control
        self.text.delete(1.0, END) # clear all
        self.text.insert(END, t) # insert sorted lines

    def cmd_insert_dir(self, event=None):
        dir_name = tkFileDialog.askdirectory(parent=self)

        if dir_name:
            self.text.insert(INSERT, os.path.normpath(dir_name))

    def cmd_insert_path(self, event=None):
        filename = tkFileDialog.askopenfilename(
            parent=self, 
            filetypes=[('All files', '*')],
            title='Select file path to insert')
        if filename:
            self.text.insert(INSERT, os.path.normpath(filename))
        

    def cmd_replace_dir(self, event=None):
        """Not being used anymore.
        """
        
        dir_name = tkFileDialog.askdirectory(parent=self)

        if dir_name:
            self.text.delete(INSERT + ' linestart', INSERT + ' lineend')
            self.text.insert(INSERT + ' linestart',
                             os.path.normpath(dir_name))

    def get_lines(self):
        """Get text as a list of lines.

        Empty lines are discarded, all lines are stripped.
        """
        
        # get all text
        t = self.text.get(1.0, END)
        # break it up into lines, also stripping extraneous whitespace
        lines = [l.strip() for l in t.split('\n') if len(l) > 0]

        return lines
        
#########################################################################
class StatusBar(Frame):
    """From the tkinterbook.
    """

    def __init__(self, master):
        Frame.__init__(self, master)
        self.label = Label(self, bd=1, relief=SUNKEN, anchor=W)
        self.label.pack(fill=X)

    def set(self, format, *args):
        self.label.config(text=format % args)
        self.label.update_idletasks()

    def clear(self):
        self.label.config(text="")
        self.label.update_idletasks()

#########################################################################
class RedirectedText(Text):
    """We would like to know when the Text widget's contents change.  We can't
    just override the insert method, we have to make use of some Tk magic.
    This magic is encapsulated in the idlelib.WidgetRedirector class which
    we use here.
    """

    def __init__(self, master=None, cnf={}, **kw):
        Text.__init__(self, master, cnf, **kw)

        # now attach the redirector
        self.redir = WidgetRedirector(self)
        self.orig_insert = self.redir.register("insert", self.new_insert)
        self.orig_delete = self.redir.register("delete", self.new_delete)

    def new_insert(self, *args):
        self.orig_insert(*args)
        self.event_generate('<<Change>>')

    def new_delete(self, *args):
        self.orig_delete(*args)
        self.event_generate('<<Change>>')

#########################################################################
class View:
    """Main user interface class.
    """
    
    def __init__(self, controller):
        self.controller = controller

        self.root = None
        self.user_text = None
        self._user_mode_label_var = None
        self._user_mode = None
        self.sys_text = None
        self._sys_mode_label_var = None
        self._sys_mode = None

        # parse text into VARIABLE = value pairs, determine type as well.
        # \w - any alphanumeric character
        # \s - any whitespace
        # usually, this expression won't match past end of line
        self.envline_po = re.compile('^\s*(\w+)\s*=\s*(.*)$')

        self._create_ui()

        self._current_text = None
        self.user_text.focus_set()

    def _create_menu(self):
        """Utility function to setup main menu.

        Called by _create_ui.
        """
        
        # MAIN MENU ####################################################
        menu = Menu(self.root)
        self.root.config(menu=menu)

        file_menu = Menu(menu, tearoff=False)
        menu.add_cascade(label="File", underline='0', menu=file_menu)
        

        # FILE ##########################################################
        file_menu.add_command(label="Save to TXT file", underline=0,
                              command=self.cmd_save, accelerator="Ctrl+S")
        self.root.bind_all("<Control-s>", self.cmd_save)
        
        file_menu.add_command(label="Load from TXT file", underline=0,
                              command=self.cmd_load, accelerator="Ctrl+L")
        self.root.bind_all("<Control-l>", self.cmd_load)
        
        file_menu.add_separator()

        file_menu.add_command(label="Insert TXT file at cursor", underline=0,
                              command=self.cmd_insert_txt)

        file_menu.add_separator()

        # currently disabled, the process needs special privileges
        #file_menu.add_command(label="Export to REG file", underline=0,
        #                      command=self.cmd_export)
        #
        #file_menu.add_separator()

        file_menu.add_command(label = "Exit", underline=1,
                              command=self.cmd_exit, accelerator="Ctrl+Q")
        self.root.bind_all("<Control-q>", self.cmd_exit)

        # EDIT ##########################################################
        edit_menu = Menu(menu, tearoff=False)
        menu.add_cascade(label="Edit", underline=0, menu=edit_menu)
        
        edit_menu.add_command(label="Cut", accelerator="Ctrl+X",
                              underline=2, command=self.cmd_cut)
        edit_menu.add_command(label="Copy", accelerator="Ctrl+C",
                              underline=0, command=self.cmd_copy)
        edit_menu.add_command(label="Paste", accelerator="Ctrl+V",
                              underline=0, command=self.cmd_paste)

        # ENVIRONMENT ###################################################
        env_menu = Menu(menu, tearoff=False)
        menu.add_cascade(label="Environment", underline=1, menu=env_menu)

        env_menu.add_command(label = "Sync with environment (Undo)",
                             underline=1,
                              command = self.cmd_sync_with_environment,
                             accelerator="F5")
        self.root.bind_all("<F5>", self.cmd_sync_with_environment)
        
        env_menu.add_command(label = "Preview changes to environment",
                             underline=0, command=self.cmd_preview,
                             accelerator="F7")
        self.root.bind_all("<F7>", self.cmd_preview)
        
        env_menu.add_command(label = "Apply to environment", underline = 0,
                              command = self.cmd_apply_to_environment,
                             accelerator="F8")
        self.root.bind_all("<F8>", self.cmd_apply_to_environment)

        # TOOLS ########################################################
        tools_menu = Menu(menu, tearoff=False)
        menu.add_cascade(label="Tools", underline=0, menu=tools_menu)
        
        tools_menu.add_command(label="Path edit current variable",
                               accelerator="F3",
                               underline=0, command=self.cmd_path_edit)
        self.root.bind_all("<F3>", self.cmd_path_edit)

        tools_menu.add_command(label="Insert directory at cursor",
                               underline=7, command=self.cmd_insert_dir)

        tools_menu.add_command(label="Insert file path at cursor",
                               underline=7, command=self.cmd_insert_path)
        

        # HELP ##########################################################
        help_menu = Menu(menu, tearoff=False)
        menu.add_cascade(label="Help", underline='0', menu=help_menu)

        help_menu.add_command(label = "About", underline = 0,
                              command = self.cmd_help_about)

        # END MENU ######################################################

    def _create_ui(self):

        # these two variables determine the final dimensions of our interface
        HALF_FRAME_HEIGHT=200
        TEXT_WIDTH=80
        
        self.root = Tk()
        self.root.title(
            "envedit %s Environment Editor (c) 2006 by Charl P. Botha" %
            (self.controller.get_version(),))

        # try finding icon in resdir and in appdir
        try:
            self.iconpath = os.path.join(
                self.controller.get_resdir(), 'envedit.ico')
            self.root.iconbitmap(self.iconpath)
            
        except TclError:
            try:
                self.iconpath = os.path.join(
                    self.controller.get_appdir(), 'envedit.ico')
                self.root.iconbitmap(self.iconpath)
                
            except TclError:
                self.show_error('Error', 'Error opening icon.  Continuing.')

        # create menu ###################################################
        self._create_menu()

        # separator after menu ##########################################
        separator = Frame(self.root, height=2, bd=1, relief=SUNKEN)
        separator.pack(fill=X, padx=5, pady=2, side=TOP)

        # setup statusbar ###############################################
        # first pack this before panedwindow, else behaviour is unexpected
        # during sash moving and resizing
        self.statusbar = StatusBar(self.root)
        self.statusbar.set('%s', 'Welcome to envedit!')
        self.statusbar.pack(fill=X, side=BOTTOM)
        
        # the paned window ##############################################
        paned_window = PanedWindow(self.root, orient=VERTICAL)
        paned_window.pack(fill=BOTH, expand=1)
        
        top_frame = Frame(paned_window, height=HALF_FRAME_HEIGHT)
        paned_window.add(top_frame)

        bottom_frame = Frame(paned_window, height=HALF_FRAME_HEIGHT)
        paned_window.add(bottom_frame)

        # we'll use this method to create the different edit boxes
        def create_scrolled_text(master, label_text):

            label_frame = Frame(master)

            l = Label(label_frame, text = label_text,
                       anchor = W)
            l.pack(side=LEFT)

            mode_label_var = StringVar()
            mode_label_var.set("")
            ml = Label(label_frame, textvariable = mode_label_var, anchor = W)
            ml.pack(side=LEFT)

            label_frame.pack()

            yscrollbar = Scrollbar(master)
            yscrollbar.pack(side=RIGHT, fill=Y)

            # this determines the width of the complete interface (yes)
            text = RedirectedText(master, height=15, width=TEXT_WIDTH,
                                  wrap=CHAR,
                                  yscrollcommand=yscrollbar.set)

            text.pack(fill=BOTH, expand=1)

            #xscrollbar.config(command=text.xview)
            yscrollbar.config(command=text.yview)

            return text, mode_label_var


        # setup user_text ###############################################
        self.user_text, self._user_mode_label_var = \
                        create_scrolled_text(top_frame, "User Environment")

        def cb_ut_fi(event):
            self.set_current_text(CURTEXT_USER)

        self.user_text.bind('<FocusIn>', cb_ut_fi)

        def cb_ut_m(event):
            self.set_user_mode(MODE_MODIFIED)

        self.user_text.bind('<<Change>>', cb_ut_m)

        # setup sys_text ################################################
        self.sys_text, self._sys_mode_label_var = \
                      create_scrolled_text(bottom_frame, "System Environment")

        def cb_st_fi(event):
            self.set_current_text(CURTEXT_SYS)

        self.sys_text.bind('<FocusIn>', cb_st_fi)

        def cb_st_c(event):
            self.set_sys_mode(MODE_MODIFIED)
            
        self.sys_text.bind('<<Change>>', cb_st_c)

        # bind event to paned_window ####################################
        def event_configure(self, event=None):
            """At every configure event of the paned_window, check that the
            user hasn't shrunken the window so that one of the panes has
            completely disappeared.  If this is the case, reset the sash
            position to half-way.
            """
            
            sash_pos = paned_window.sash_coord(0)
            pw_height = paned_window.winfo_height()
            if sash_pos[1] + 20 > pw_height:
                paned_window.sash_place(0, sash_pos[0], pw_height / 2)
            
        paned_window.bind('<Configure>', event_configure)

        
        # finish UI creation ###########################################

        # call update so we know that sizes are up to date
        self.root.update_idletasks()
        # now set the minsize so that things can not disappear
        self.root.minsize(self.root.winfo_width(), self.root.winfo_height())

    def close(self):
        self.root.destroy()

    def cmd_apply_to_environment(self, event=None):
        if self._current_text == CURTEXT_USER:
            try:
                self.controller.apply_user_env()
                
            except EnvEditException, e:
                tkMessageBox.showerror(
                    'Error',
                    'Could not apply changes to the USER environment: %s' %
                    (str(e),))
                
            except EnvEditWarningException, e:
                tkMessageBox.showwarning(
                    'Warning', str(e))

            else:
                self.statusbar.set(
                    'Successfully applied changes to USER environment.')
                
        else:
            try:
                self.controller.apply_sys_env()
                
            except EnvEditException, e:
                tkMessageBox.showerror(
                    'Error',
                    'Could not apply changes to the USER environment: %s' %
                    (str(e),))
                
            except EnvEditWarningException, e:
                tkMessageBox.showwarning(
                    'Warning', str(e))

            else:
                self.statusbar.set(
                    'Successfully applied changes to SYSTEM environment.')

    def cmd_cut(self):
        t = self.get_text_with_focus()
        if t:
            t.event_generate('<<Cut>>')

    def cmd_copy(self):
        t = self.get_text_with_focus()
        if t:
            t.event_generate('<<Copy>>')
        
    def cmd_paste(self):
        t = self.get_text_with_focus()
        if t:
            t.event_generate('<<Paste>>')

    def cmd_path_edit(self, event=None):
        t = self.get_text_with_focus()

        if not t:
            return

        # this is how you get the current line, weird, I know.
        curline = t.get(INSERT + ' linestart', INSERT + ' lineend')

        # now parse it to take apart something = s1;s2;s with spaces;s4
        mo = self.envline_po.search(curline)
        if mo:
            paths = [p.strip() for p in mo.group(2).split(';') if len(p) > 0]

            # now pop up our little dialog and stuff
            d = PathEditDialog(
                self.root, paths=paths,
                title="Variable: %s" % (mo.group(1),),
                iconpath=self.iconpath)

            if d.new_value is not None:
                # this means the user applied, so we have to make
                # the changes...
                t.delete(INSERT + ' linestart', INSERT + ' lineend')
                t.insert(INSERT + ' linestart', '%s = %s' %
                         (mo.group(1), d.new_value))

            # also put the focus back where it was
            # after escaping from the path edit, we have a current text,
            # but the focus is elsewhere...
            if self.get_current_text() == CURTEXT_USER:
                self.user_text.focus_set()

            else:
                self.sys_text.focus_set()

    def cmd_help_about(self):
        tkMessageBox.showinfo(
            'Help | About',
            'envedit %s is copyright 2006 by Charl P. Botha '
            '<http://cpbotha.net/>\n\n'
            'Because the Windows environment editor will '
            'ruin your day, every time.\n\n'
            'That being said, YOU ARE USING ENVEDIT '
            'ENTIRELY *AT YOUR OWN RISK*.' % (self.controller.get_version(),),
            parent = self.root)

    def cmd_insert_dir(self, event=None):
        """Insert browsed directory at current cursor position.
        """
        
        t = self.get_text_with_focus()
        if not t:
            return
        
        dir_name = tkFileDialog.askdirectory(parent=self.root)

        if dir_name:
            t.insert(INSERT, os.path.normpath(dir_name))

    def cmd_insert_path(self, event=None):
        """Insert browsed file path at current cursor position.
        """

        t = self.get_text_with_focus()
        if not t:
            return
        
        filename = tkFileDialog.askopenfilename(
            parent=self.root, 
            filetypes=[('All files', '*')],
            title='Select file path to insert')

        if filename:
            t.insert(INSERT, os.path.normpath(filename))

    def cmd_insert_txt(self, event=None):
        """Insert text loaded from file at current cursor position in current
        environment text edit.
        """
        
        text_widget = self.get_text_with_focus()
        if not text_widget:
            return
        
        current_text = self.get_current_text_as_string()
        filename = tkFileDialog.askopenfilename(
            parent=self.root, 
            filetypes=[('Text files', '*.txt'), ('All files', '*')],
            title='Insert TXT file at cursor in %s environment view' % \
            (current_text,))

        if len(filename) > 0:

            try:
                tf = file(filename, 'r')
                txt = tf.read()
                tf.close()
            except IOError, e:
                tkMessageBox.showerror(
                    'File load error',
                    'Error reading from %s: %s' % (filename, str(e)))
            else:
                # insert the text at the current point
                text_widget.insert(INSERT, txt)
                
                if self.get_current_text() == CURTEXT_USER:
                    self.set_user_mode(MODE_MODIFIED)

                else:
                    self.set_sys_mode(MODE_MODIFIED)

                self.statusbar.set(
                    'Inserted %s at cursor in %s environment view',
                    filename, current_text)
        

    def cmd_load(self, event=None):
        """We handle the complete load request here in the view, as the
        processing is quite trivial and only affects the view.
        """
        
        current_text = self.get_current_text_as_string()
        filename = tkFileDialog.askopenfilename(
            parent=self.root, 
            filetypes=[('Text files', '*.txt'), ('All files', '*')],
            title='Load file into %s environment view' % (current_text,))

        if len(filename) > 0:

            try:
                tf = file(filename, 'r')
                txt = tf.read()
                tf.close()
            except IOError, e:
                tkMessageBox.showerror(
                    'File load error',
                    'Error reading from %s: %s' % (filename, str(e)))
            else:
                if self.get_current_text() == CURTEXT_USER:
                    self.set_user_text(txt)
                    self.set_user_mode(MODE_MODIFIED)

                else:
                    self.set_sys_text(txt)
                    self.set_sys_mode(MODE_MODIFIED)

                self.statusbar.set(
                    'Successfully loaded %s environment view from %s',
                    current_text, filename)

    def cmd_preview(self, event=None):
        if self.get_current_text() == CURTEXT_USER:
            self.controller.preview_user_env()

        else:
            self.controller.preview_sys_env()

    def cmd_save(self, event=None):
        """We handle the complete save request here in the view, as the
        processing is quite trivial and only affects the view.
        """

        current_text = self.get_current_text_as_string()
        filename = tkFileDialog.asksaveasfilename(
            parent=self.root,
            filetypes=[('Text files', '*.txt'), ('All files', '*')],
            title="Save %s environment view to file ..." % (current_text,))
        
        if len(filename) > 0:
            # if the user has not supplied any extension, we give it .txt
            name, ext = os.path.splitext(filename)
            if not ext:
                filename += '.txt'
            
            if self.get_current_text() == CURTEXT_USER:
                txt = self.get_user_text()

            else:
                txt = self.get_sys_text()

            try:
                tf = file(filename, 'w')
                tf.write(txt)
                tf.close()
            except IOError, e:
                tkMessageBox.showerror(
                    'File save error',
                    'Error writing to %s: %s' % (filename, str(e)))

            else:
                self.statusbar.set(
                    'Successfully wrote %s environment view to %s.',
                    current_text, filename)

    def cmd_sync_with_environment(self, event=None):
        if self._current_text == CURTEXT_USER:
            try:
                self.controller.sync_user_env()
            except EnvEditException, e:
                self.show_error(
                    'Error',
                    'Could not synchronise with USER environment: %s' %
                    (str(e),))
                
        else:
            try:
                self.controller.sync_sys_env()
            except EnvEditException, e:
                self.show_error(
                    'Error',
                    'Could not synchronise with SYSTEM environment: %s' %
                    (str(e),))
            

    def cmd_exit(self, event=None):
        self.controller.quit()

    def cmd_export(self, event=None):
        """Currently unused, the process needs special privileges for this
        to work.
        """
        
        current_text = self.get_current_text_as_string()
        filename = tkFileDialog.asksaveasfilename(
            parent=self.root,
            filetypes=[('REG files', '*.reg'), ('All files', '*')],
            title="Save %s environment view to REG file ..." %
            (current_text,))
        
        if len(filename) > 0:
            try:
                if self._current_text == CURTEXT_USER:
                    self.controller.export_user_to_reg_file(filename)
                else:
                    self.controller.export_sys_to_reg_file(filename)

            except EnvEditException, e:
                self.show_error(
                    'Error',
                    'Could not export %s environment to REG file: %s' %
                    (current_text, str(e)))

    def main_loop(self):
        self.root.mainloop()

    def get_current_text(self):
        """Use to determine which text has the focus.

        @return: CURTEXT_USER and CURTEXT_SYS for user and sys text
        respectively.
        """
        return self._current_text

    def get_current_text_as_string(self):
        if self.get_current_text() == CURTEXT_USER:
            return 'USER'
        else:
            return 'SYSTEM'

    def get_text_with_focus(self):
        """Return the actual text widget that currently has the focus.
        """
        
        if self.root.focus_get() == self.user_text:
            return self.user_text
        elif self.root.focus_get() == self.sys_text:
            return self.sys_text
        else:
            return None
            

    def set_current_text(self, which):
        """Set current text.

        This is the text that has the focus (and this method is often called
        as part of the FocusIn event handler).  The background colours of
        the two text controls will be adapted accordingly.

        @param which: CURTXT_USER or CURTEXT_SYS.
        """
        
        self._current_text = which
        if self._current_text == CURTEXT_USER:
            ut_col = 'lightblue'
            st_col = 'white'

        else:
            ut_col = 'white'
            st_col = 'lightblue'

        self.user_text.config(bg=ut_col)
        self.sys_text.config(bg=st_col)

    def get_user_text(self):
        """Get and return all text in user edit box.
        """

        return self.user_text.get(1.0, END)

    def get_sys_text(self):
        """Get and return all text in sys edit box.
        """

        return self.sys_text.get(1.0, END)

    def set_text(self, text_widget, text):
        """Replace the contents of text_widget with text.

        Used by set_user_text and set_sys_text.
        """
        
        text_widget.delete(1.0, END) # clear all
        text_widget.insert(END, text)

    def set_user_text(self, txt):
        """Replace the contents of the user_text with txt.
        """

        self.set_text(self.user_text, txt)

    def set_sys_text(self, txt):
        """Replace the contents of the sys_text with txt.
        """

        self.set_text(self.sys_text, txt)

    def get_user_mode(self):
        """Return current mode of user environment.

        @returns: MODE_SYNCED, MODE_MODIFIED or MODE_PREVIEW.
        """
        
        return self._user_mode

    def set_user_mode(self, mode):
        """Change mode of user text input.

        @param mode: one of MODE_SYNCED, MODE_MODIFIED or MODE_PREVIEW.
        """
        
        if mode != self._user_mode:
            self._user_mode = mode
            self._user_mode_label_var.set(MODE_TO_TEXT[self._user_mode])

    def set_sys_mode(self, mode):
        """Change mode of system text input.

        @param mode: one of MODE_SYNCED, MODE_MODIFIED or MODE_PREVIEW.
        """
        
        if mode != self._sys_mode:
            self._sys_mode = mode
            self._sys_mode_label_var.set(MODE_TO_TEXT[self._sys_mode])

    def get_sys_mode(self):
        """Return current mode of system environment.

        @returns: MODE_SYNCED, MODE_MODIFIED or MODE_PREVIEW.
        """
        
        return self._sys_mode

    def show_error(self, title, msg):
        tkMessageBox.showerror(title, msg)


